package net.entelijan.cobean.multisel;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.ListModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import net.entelijan.cobean.core.ColumnDesc;
import net.entelijan.cobean.core.IInitializer;
import net.entelijan.cobean.core.IMultiSelectionListCobean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

public class DefaultMultiselInitializer<T> implements
		IInitializer<IMultiSelectionListCobean<T>> {

	private static Log log = LogFactory
			.getLog(DefaultMultiselInitializer.class);

	private List<IListItemWrapper<T>> leftItems = new ArrayList<IListItemWrapper<T>>();

	private List<IListItemWrapper<T>> rightItems = new ArrayList<IListItemWrapper<T>>();

	private JList coLeftList;

	private JList coRightList;

	private JButton coAddButton;

	private JButton coRemoveButton;

	private List<ColumnDesc> columnDescs;

	private Comparator<T> comparator;

	private boolean sorted = true;

	public DefaultMultiselInitializer() {
		super();
	}

	public IMultiSelectionListCobean<T> initialize() {
		coLeftList.addListSelectionListener(new ListSelectionListener() {

			public void valueChanged(ListSelectionEvent e) {
				if (e.getValueIsAdjusting()) {
					coRightList.setSelectedIndices(new int[0]);
				}
			}

		});

		// Unselect the other side on selection
		coRightList.addListSelectionListener(new ListSelectionListener() {

			public void valueChanged(ListSelectionEvent e) {
				if (e.getValueIsAdjusting()) {
					coLeftList.setSelectedIndices(new int[0]);
				}
			}

		});

		coAddButton.addActionListener(new AddActionListener());
		coRemoveButton.addActionListener(new RemoveAction());

		updateListComponents();

		return new IMultiSelectionListCobean<T>() {

			public List<T> getValue() {
				return unwrapAll(leftItems);
			}

			public List<T> getSelectedValue() {
				return unwrapAll(rightItems);
			}

			public void setValue(List<T> data) {
				if (data != null) {
					leftItems = wrapAll(data);
				}
				List<T> selItems = unwrapAll(rightItems);
				selItems = filterNotContained(selItems, leftItems);
				rightItems = wrapAll(selItems);
				updateListComponents();
			}

			public void setSelectedValue(List<T> data) {
				List<T> selItems = data;
				if (selItems == null) {
					selItems = new ArrayList<T>();
				}
				selItems = filterNotContained(selItems, leftItems);
				rightItems = wrapAll(selItems);
				updateListComponents();
			}

		};
	}

	private List<T> filterNotContained(List<T> items,
			List<IListItemWrapper<T>> containing) {
		ArrayList<T> re = new ArrayList<T>();
		for (T item : items) {
			if (containing(item, containing)) {
				re.add(item);
			}
		}
		return re;
	}

	private boolean containing(T item, List<IListItemWrapper<T>> containing) {
		boolean re = false;
		for (IListItemWrapper<T> cItem : containing) {
			if (item.equals(cItem.getWrappedObject())) {
				re = true;
				break;
			}
		}
		return re;
	}

	protected int compare(T litem, T ritem) {
		int re;
		if (this.comparator != null) {
			re = this.comparator.compare(litem, ritem);
		} else {
			re = 0;
		}
		return re;
	}

	protected String toDisplayString(T value) {
		String re;
		if (columnDescs == null || columnDescs.isEmpty()) {
			re = "" + value;
		} else {
			StringBuilder sb = new StringBuilder();
			if (value != null) {
				BeanWrapper wvalue = new BeanWrapperImpl(value);
				int cnt = 0;
				for (ColumnDesc cd : columnDescs) {
					if (cnt++ > 0) {
						sb.append(" ");
					}
					Object val = wvalue.getPropertyValue(cd.getPropertyName());
					sb.append(val == null ? "" : val);
				}
			}
			re = sb.toString();
		}
		return re;
	}

	public boolean isSorted() {
		return sorted;
	}

	public void setSorted(boolean sorted) {
		this.sorted = sorted;
	}

	@SuppressWarnings("unchecked")
	private void updateListComponents() {
		List<IListItemWrapper<T>> left = getLeftItemsWithoutRightItems();
		if (sorted) {
			Collections.sort(left);
		}
		coLeftList.setModel(createDefaultListModel(left));
		List<IListItemWrapper<T>> right = this.rightItems;
		if (sorted) {
			Collections.sort(right);
		}
		coRightList.setModel(createDefaultListModel(right));
	}

	private List<IListItemWrapper<T>> getLeftItemsWithoutRightItems() {
		ArrayList<IListItemWrapper<T>> re = new ArrayList<IListItemWrapper<T>>();
		Set<IListItemWrapper<T>> rightSet = new HashSet<IListItemWrapper<T>>(
				this.rightItems);
		for (IListItemWrapper<T> item : this.leftItems) {
			if (!rightSet.contains(item)) {
				re.add(item);
			}
		}
		return re;
	}

	private DefaultListModel createDefaultListModel(
			List<IListItemWrapper<T>> items) {
		DefaultListModel model = new DefaultListModel();
		for (Object item : items) {
			model.addElement(item);
		}
		return model;
	}

	private List<IListItemWrapper<T>> wrapAll(List<T> items) {
		ArrayList<IListItemWrapper<T>> re = new ArrayList<IListItemWrapper<T>>();
		for (T item : items) {
			re.add(new AbstractListItemWrapper<T>(item) {

				@Override
				protected int compare(T litem, T ritem) {
					return DefaultMultiselInitializer.this
							.compare(litem, ritem);
				}

				@Override
				protected String toDisplayString(T item) {
					return DefaultMultiselInitializer.this
							.toDisplayString(item);
				}

			});
		}
		return re;
	}

	private List<T> unwrapAll(List<IListItemWrapper<T>> witems) {
		ArrayList<T> re = new ArrayList<T>();
		for (IListItemWrapper<T> witem : witems) {
			re.add(witem.getWrappedObject());
		}
		return re;
	}

	private int[] getIndices(List<IListItemWrapper<T>> selectedItems,
			ListModel model) {
		int[] re = new int[selectedItems.size()];
		int i = 0;
		for (IListItemWrapper<T> item : selectedItems) {
			re[i++] = getIndex(model, item);
		}
		return re;
	}

	@SuppressWarnings("unchecked")
	private int getIndex(ListModel model, IListItemWrapper<T> item) {
		int re = -1;
		for (int i = 0; i < model.getSize(); i++) {
			IListItemWrapper<T> elem = (IListItemWrapper<T>) model
					.getElementAt(i);
			if (elem.equals(item)) {
				re = i;
				break;
			}
		}
		return re;
	}

	@SuppressWarnings("unchecked")
	private List<IListItemWrapper<T>> getSelectedItems(JList list) {
		Object[] selectedValues = list.getSelectedValues();
		ArrayList<IListItemWrapper<T>> re = new ArrayList<IListItemWrapper<T>>();
		for (Object obj : selectedValues) {
			re.add((IListItemWrapper<T>) obj);
		}
		return re;
	}

	public class AddActionListener implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			List<IListItemWrapper<T>> selectedItems = getSelectedItems(coLeftList);
			rightItems.addAll(selectedItems);
			updateListComponents();
			int[] selIndices = getIndices(selectedItems, coRightList.getModel());
			coRightList.setSelectedIndices(selIndices);
			log.debug("[actionPerformed]");
		}

	}

	public class RemoveAction implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			List<IListItemWrapper<T>> selectedItems = getSelectedItems(coRightList);
			rightItems.removeAll(selectedItems);
			updateListComponents();
			int[] selIndices = getIndices(selectedItems, coLeftList.getModel());
			coLeftList.setSelectedIndices(selIndices);
			log.debug("[actionPerformed]");
		}

	}

	public JList getCoLeftList() {
		return coLeftList;
	}

	public void setCoLeftList(JList coLeftList) {
		this.coLeftList = coLeftList;
	}

	public JList getCoRightList() {
		return coRightList;
	}

	public void setCoRightList(JList coRightList) {
		this.coRightList = coRightList;
	}

	public JButton getCoAddButton() {
		return coAddButton;
	}

	public void setCoAddButton(JButton coAddButton) {
		this.coAddButton = coAddButton;
	}

	public JButton getCoRemoveButton() {
		return coRemoveButton;
	}

	public void setCoRemoveButton(JButton coRemoveButton) {
		this.coRemoveButton = coRemoveButton;
	}

	public List<ColumnDesc> getColumnDescs() {
		return columnDescs;
	}

	public void setColumnDescs(List<ColumnDesc> columnDescs) {
		this.columnDescs = columnDescs;
	}

	public Comparator<T> getComparator() {
		return comparator;
	}

	public void setComparator(Comparator<T> comparator) {
		this.comparator = comparator;
	}

}
